import os
import secrets
import logging
from datetime import datetime, timedelta
from flask import Flask, render_template, request, redirect, url_for, send_file, flash, session, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
import mimetypes
from logging.handlers import RotatingFileHandler

app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(16)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///filecodebox.db'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 200 * 1024 * 1024  # 200MB
app.config['DEFAULT_EXPIRE_DAYS'] = 7
app.config['ALLOWED_EXTENSIONS'] = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'zip', 'rar', 'mp4', 'mov', 'avi', 'mp3', 'wav'}
app.config['DEFAULT_ADMIN_PASSWORD'] = 'admin123'

# 配置日志
handler = RotatingFileHandler('filecodebox.log', maxBytes=10000, backupCount=3)
handler.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter(
    '[%(asctime)s] %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
app.logger.addHandler(handler)

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

db = SQLAlchemy(app)

from flask import session

# 在应用配置中添加会话密钥
app.config['SECRET_KEY'] = secrets.token_hex(16)  # 确保使用强密钥

class FileEntry(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    filename = db.Column(db.String(255), nullable=False)
    filepath = db.Column(db.String(255), nullable=False)
    file_size = db.Column(db.Integer, nullable=False)
    file_type = db.Column(db.String(50), nullable=False)
    access_code = db.Column(db.String(6), unique=True, nullable=False)
    expire_time = db.Column(db.DateTime, nullable=False)
    download_count = db.Column(db.Integer, default=0)
    max_downloads = db.Column(db.Integer, default=1)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    upload_ip = db.Column(db.String(45))
    last_download_ip = db.Column(db.String(45))

class SystemConfig(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    max_upload_size = db.Column(db.Integer, default=200 * 1024 * 1024)
    allowed_extensions = db.Column(db.String(255), default='txt,pdf,png,jpg,jpeg,gif,doc,docx,xls,xlsx,zip,rar,mp4,mov,avi,mp3,wav')
    allow_guest_uploads = db.Column(db.Boolean, default=True)
    site_title = db.Column(db.String(100), default='FileCodeBox')
    logo_url = db.Column(db.String(255), default='')
    primary_color = db.Column(db.String(7), default='#4361ee')
    default_expire_days = db.Column(db.Integer, default=7)
    admin_password = db.Column(db.String(128), default='')

# 创建数据库表并初始化
with app.app_context():
    db.create_all()
    config = SystemConfig.query.first()
    if not config:
        config = SystemConfig()
        config.admin_password = generate_password_hash(app.config['DEFAULT_ADMIN_PASSWORD'])
        db.session.add(config)
        db.session.commit()

# 获取系统配置
def get_system_config():
    return SystemConfig.query.first()

# 验证管理员密码
def verify_admin_password(password):
    config = get_system_config()
    return config and config.admin_password and check_password_hash(config.admin_password, password)

# 检查文件扩展名是否允许
def allowed_file(filename):
    config = get_system_config()
    allowed_extensions = config.allowed_extensions.split(',')
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions

# 上下文处理器 - 向所有模板注入变量
@app.context_processor
def inject_common():
    config = get_system_config()
    current_year = datetime.utcnow().year
    
    return dict(
        config=config,
        current_year=current_year,
        now=datetime.utcnow()
    )

@app.route('/')
def index():
    config = get_system_config()
    return render_template('index.html', config=config)

@app.route('/share', methods=['POST'])
def share_file():
    """文件分享核心功能"""
    config = get_system_config()
    
    # 检查是否允许访客上传
    if not config.allow_guest_uploads:
        return jsonify({
            'success': False,
            'message': '当前系统不允许访客上传文件'
        })
    
    # 获取表单数据
    expire_days = int(request.form.get('expire_days', config.default_expire_days))
    max_downloads = int(request.form.get('max_downloads', 1))
    
    # 检查文件是否存在
    if 'file' not in request.files:
        return jsonify({
            'success': False,
            'message': '未选择文件'
        })
    
    file = request.files['file']
    if file.filename == '':
        return jsonify({
            'success': False,
            'message': '未选择文件'
        })
    
    # 检查文件类型
    if not allowed_file(file.filename):
        return jsonify({
            'success': False,
            'message': '不允许上传此类型的文件'
        })
    
    # 生成6位提取码
    access_code = secrets.token_hex(3).upper()[:6]
    expire_time = datetime.utcnow() + timedelta(days=expire_days)
    
      # 保存文件
    # 使用原始文件名，但确保安全
    original_filename = secure_filename(file.filename)
    filename = f"{secrets.token_hex(8)}_{original_filename}"
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(filepath)
    
    # 获取文件信息
    file_size = os.path.getsize(filepath)
    
    # 检查文件大小
    if file_size > config.max_upload_size:
        os.remove(filepath)
        return jsonify({
            'success': False,
            'message': f'文件大小超过限制 (最大 {config.max_upload_size//(1024 * 1024)} MB)'
        })
    
    # 获取文件MIME类型
    file_type, _ = mimetypes.guess_type(filepath)
    if file_type is None:
        file_type = 'application/octet-stream'
    
    new_file = FileEntry(
        filename=original_filename,
        filepath=filepath,
        file_size=file_size,
        file_type=file_type,
        access_code=access_code,
        expire_time=expire_time,
        max_downloads=max_downloads,
        upload_ip=request.remote_addr
    )
    
    db.session.add(new_file)
    db.session.commit()
    
    app.logger.info(f'文件上传: {file.filename}, 提取码: {access_code}, 来自IP: {request.remote_addr}')
    
    return jsonify({
        'success': True,
        'access_code': access_code,
        'filename': file.filename,
        'file_size': file_size,
        'expire_time': expire_time.strftime('%Y-%m-%d %H:%M'),
        'max_downloads': max_downloads
    })

@app.route('/download', methods=['POST'])
def download_file():
    """文件下载功能"""
    access_code = request.form.get('access_code', '').strip().upper()
    if not access_code:
        return jsonify({
            'success': False,
            'message': '请输入提取码'
        })
    
    file_entry = FileEntry.query.filter_by(access_code=access_code).first()
    
    if not file_entry:
        return jsonify({
            'success': False,
            'message': '无效提取码或文件已过期'
        })
    
    if file_entry.expire_time < datetime.utcnow():
        return jsonify({
            'success': False,
            'message': '文件已过期'
        })
    
    if file_entry.download_count >= file_entry.max_downloads:
        return jsonify({
            'success': False,
            'message': '超过最大下载次数'
        })
    
    # 更新下载计数和最后下载IP
    file_entry.download_count += 1
    file_entry.last_download_ip = request.remote_addr
    db.session.commit()
    
    # 记录下载日志
    app.logger.info(f'文件下载: {file_entry.filename}, 提取码: {access_code}, 来自IP: {request.remote_addr}')
    
    # 处理文件下载 - 修复文件名问题
    try:
        # 获取文件扩展名
        _, file_extension = os.path.splitext(file_entry.filename)
        
        # 创建安全的下载文件名
        safe_filename = secure_filename(file_entry.filename)
        
        # 如果文件名被修改，保留原始扩展名
        if not safe_filename.endswith(file_extension):
            safe_filename += file_extension
        
        # 发送文件并设置正确的文件名
        return send_file(
            file_entry.filepath,
            as_attachment=True,
            download_name=safe_filename,
            mimetype=file_entry.file_type
        )
    except FileNotFoundError:
        return jsonify({
            'success': False,
            'message': '文件不存在'
        })

@app.route('/admin/')
def admin_login():
    return render_template('admin_login.html')

@app.route('/admin/dashboard', methods=['GET', 'POST'])
def admin_dashboard():
    """管理后台"""
    # 如果是 POST 请求，处理登录表单
    if request.method == 'POST':
        password = request.form['password']
        
        if not verify_admin_password(password):
            flash('密码错误', 'error')
            return redirect(url_for('admin_login'))
        
        # 设置管理员会话
        session['admin_logged_in'] = True
    
    # 检查是否已登录
    if not session.get('admin_logged_in'):
        return redirect(url_for('admin_login'))
    
    # 获取所有文件记录
    files = FileEntry.query.order_by(FileEntry.created_at.desc()).all()
    
    # 计算存储空间使用
    total_size = sum(f.file_size for f in files)
    
    stats = {
        'total_files': len(files),
        'active_files': sum(1 for f in files if f.expire_time > datetime.utcnow()),
        'expired_files': sum(1 for f in files if f.expire_time <= datetime.utcnow()),
        'total_size': total_size,
        'total_size_mb': total_size / (1024 * 1024),
        'today_uploads': sum(1 for f in files if f.created_at.date() == datetime.utcnow().date()),
        'today_downloads': sum(f.download_count for f in files if f.created_at.date() == datetime.utcnow().date())
    }
    
    app.logger.info(f"管理面板被访问，IP: {request.remote_addr}")
    return render_template('admin_dashboard.html', files=files, stats=stats)

@app.route('/admin/update_config', methods=['POST'])
def update_config():
    """更新系统配置"""
    if not session.get('admin_logged_in'):
        flash('请先登录', 'error')
        return redirect(url_for('admin_login'))
    
    config = SystemConfig.query.first()
    
    # 更新配置
    config.max_upload_size = int(request.form.get('max_upload_size')) * 1024 * 1024
    config.allowed_extensions = request.form.get('allowed_extensions')
    config.allow_guest_uploads = 'allow_guest_uploads' in request.form
    config.site_title = request.form.get('site_title', 'FileCodeBox')
    config.logo_url = request.form.get('logo_url', '')
    config.primary_color = request.form.get('primary_color', '#4361ee')
    config.default_expire_days = int(request.form.get('default_expire_days', 7))
    
    db.session.commit()
    
    app.logger.info(f"系统配置更新: {config.site_title}")
    flash('配置已更新', 'success')
    return redirect(url_for('admin_dashboard'))

@app.route('/admin/change_password', methods=['POST'])
def change_password():
    """修改管理员密码"""
    if not session.get('admin_logged_in'):
        flash('请先登录', 'error')
        return redirect(url_for('admin_login'))
    
    config = SystemConfig.query.first()
    current_password = request.form.get('current_password')
    new_password = request.form.get('new_password')
    confirm_password = request.form.get('confirm_password')
    
    # 验证当前密码
    if not verify_admin_password(current_password):
        flash('当前密码错误', 'error')
        return redirect(url_for('admin_dashboard'))
    
    # 验证新密码
    if new_password != confirm_password:
        flash('两次输入的密码不一致', 'error')
        return redirect(url_for('admin_dashboard'))
    
    if len(new_password) < 6:
        flash('密码长度至少为6位', 'error')
        return redirect(url_for('admin_dashboard'))
    
    # 更新密码
    config.admin_password = generate_password_hash(new_password)
    db.session.commit()
    
    flash('密码已更新', 'success')
    return redirect(url_for('admin_dashboard'))

@app.route('/admin/delete/<int:file_id>', methods=['POST'])
def delete_file(file_id):
    """删除文件记录"""
    if not session.get('admin_logged_in'):
        flash('请先登录', 'error')
        return redirect(url_for('admin_login'))
    
    file_entry = FileEntry.query.get_or_404(file_id)
    
    # 物理删除文件
    if os.path.exists(file_entry.filepath):
        try:
            os.remove(file_entry.filepath)
            app.logger.info(f"文件已删除: {file_entry.filename}")
        except OSError as e:
            app.logger.error(f"删除文件失败: {e}")
    
    # 删除数据库记录
    db.session.delete(file_entry)
    db.session.commit()
    
    flash('文件已删除', 'success')
    return redirect(url_for('admin_dashboard'))

# 添加退出登录路由
@app.route('/admin/logout')
def admin_logout():
    """退出管理面板"""
    session.pop('admin_logged_in', None)
    flash('您已成功退出管理面板', 'success')
    return redirect(url_for('admin_login'))


# 清理过期文件的任务
def cleanup_expired_files():
    with app.app_context():
        expired_files = FileEntry.query.filter(FileEntry.expire_time < datetime.utcnow()).all()
        for file_entry in expired_files:
            # 物理删除文件
            if os.path.exists(file_entry.filepath):
                try:
                    os.remove(file_entry.filepath)
                    app.logger.info(f"清理过期文件: {file_entry.filename}")
                except OSError as e:
                    app.logger.error(f"删除过期文件失败: {e}")
            # 删除数据库记录
            db.session.delete(file_entry)
        db.session.commit()

if __name__ == '__main__':
    # 启动前清理过期文件
    cleanup_expired_files()
    app.run(debug=True)